実践!AWS CDK #14 ネットワーク ACL
はじめに
今回はネットワーク ACL を作成します。
これが完成すればネットワークまわりの構築は一旦完了です。
前回の記事はこちら。
AWS 構成図
今度も前回と変わらず。構成図には書きません。
設計
ネットワーク ACL は VPC 作成時に自動生成されるデフォルトのものと同じ設定にします。基本的にアクセス制御はセキュリティグループで行い、ネットワーク ACL は利用しないという思想です。
作成するネットワーク ACL は 3 つ。各層のサブネットに紐付け、AZ で区別はしません。
プロパティは以下の通りです。
リソース名 | 関連付けるサブネット |
---|---|
devio-stg-nacl-public | devio-stg-subnet-public-1a devio-stg-subnet-public-1c |
devio-stg-nacl-app | devio-stg-subnet-app-1a devio-stg-subnet-app-1c |
devio-stg-nacl-db | devio-stg-subnet-db-1a devio-stg-subnet-db-1c |
すべてのネットワーク ACL は次のように設定します。(デフォルトネットワーク ACL と同じ)
インバウンドルール
ルール番号 | タイプ | プロトコル | ポート範囲 | 送信元 | 許可/拒否 |
---|---|---|---|---|---|
100 | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Allow |
* | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Deny |
アウトバウンドルール
ルール番号 | タイプ | プロトコル | ポート範囲 | 送信先 | 許可/拒否 |
---|---|---|---|---|---|
100 | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Allow |
* | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Deny |
ルール番号 *
はネットワーク ACL を作成すると自動で付与されるので、ルール番号 100
のみ実装します。
実装
ネットワーク ACL に関する処理を行うクラスはこちら。
import * as cdk from '@aws-cdk/core'; import { CfnNetworkAcl, CfnNetworkAclEntry, CfnSubnetNetworkAclAssociation, CfnVPC, CfnSubnet } from '@aws-cdk/aws-ec2'; import { Resource } from './abstract/resource'; interface AssociationInfo { readonly id: string; readonly subnetId: () => string; } interface ResourceInfo { readonly id: string; readonly resourceName: string; readonly entryIdInbound: string; readonly entryIdOutbound: string; readonly associations: AssociationInfo[]; readonly assign: (networkAcl: CfnNetworkAcl) => void; } export class NetworkAcl extends Resource { public public: CfnNetworkAcl; public app: CfnNetworkAcl; public db: CfnNetworkAcl; private readonly vpc: CfnVPC; private readonly subnetPublic1a: CfnSubnet; private readonly subnetPublic1c: CfnSubnet; private readonly subnetApp1a: CfnSubnet; private readonly subnetApp1c: CfnSubnet; private readonly subnetDb1a: CfnSubnet; private readonly subnetDb1c: CfnSubnet; private readonly resources: ResourceInfo[] = [ { id: 'NetworkAclPublic', resourceName: 'nacl-public', entryIdInbound: 'NetworkAclEntryInboundPublic', entryIdOutbound: 'NetworkAclEntryOutboundPublic', associations: [ { id: 'NetworkAclAssociationPublic1a', subnetId: () => this.subnetPublic1a.ref }, { id: 'NetworkAclAssociationPublic1c', subnetId: () => this.subnetPublic1c.ref } ], assign: networkAcl => this.public = networkAcl }, { id: 'NetworkAclApp', resourceName: 'nacl-app', entryIdInbound: 'NetworkAclEntryInboundApp', entryIdOutbound: 'NetworkAclEntryOutboundApp', associations: [ { id: 'NetworkAclAssociationApp1a', subnetId: () => this.subnetApp1a.ref }, { id: 'NetworkAclAssociationApp1c', subnetId: () => this.subnetApp1c.ref } ], assign: networkAcl => this.app = networkAcl }, { id: 'NetworkAclDb', resourceName: 'nacl-db', entryIdInbound: 'NetworkAclEntryInboundDb', entryIdOutbound: 'NetworkAclEntryOutboundDb', associations: [ { id: 'NetworkAclAssociationDb1a', subnetId: () => this.subnetDb1a.ref }, { id: 'NetworkAclAssociationDb1c', subnetId: () => this.subnetDb1c.ref } ], assign: networkAcl => this.db = networkAcl } ]; constructor( vpc: CfnVPC, subnetPublic1a: CfnSubnet, subnetPublic1c: CfnSubnet, subnetApp1a: CfnSubnet, subnetApp1c: CfnSubnet, subnetDb1a: CfnSubnet, subnetDb1c: CfnSubnet ) { super(); this.vpc = vpc; this.subnetPublic1a = subnetPublic1a; this.subnetPublic1c = subnetPublic1c; this.subnetApp1a = subnetApp1a; this.subnetApp1c = subnetApp1c; this.subnetDb1a = subnetDb1a; this.subnetDb1c = subnetDb1c; } createResources(scope: cdk.Construct) { for (const resourceInfo of this.resources) { const networkAcl = this.createNetworkAcl(scope, resourceInfo); resourceInfo.assign(networkAcl); } } private createNetworkAcl(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnNetworkAcl { const networkAcl = new CfnNetworkAcl(scope, resourceInfo.id, { vpcId: this.vpc.ref, tags: [{ key: 'Name', value: this.createResourceName(scope, resourceInfo.resourceName) }] }); this.createEntry(scope, resourceInfo.entryIdInbound, networkAcl, false); this.createEntry(scope, resourceInfo.entryIdOutbound, networkAcl, true); for (const associationInfo of resourceInfo.associations) { this.createAssociation(scope, associationInfo, networkAcl); } return networkAcl; } private createEntry(scope: cdk.Construct, id: string, networkAcl: CfnNetworkAcl, egress: boolean) { const entry = new CfnNetworkAclEntry(scope, id, { networkAclId: networkAcl.ref, protocol: -1, ruleAction: 'allow', ruleNumber: 100, cidrBlock: '0.0.0.0/0' }); if (egress) entry.egress = true; } private createAssociation(scope: cdk.Construct, associationInfo: AssociationInfo, networkAcl: CfnNetworkAcl) { new CfnSubnetNetworkAclAssociation(scope, associationInfo.id, { networkAclId: networkAcl.ref, subnetId: associationInfo.subnetId() }); } }
今回も前回のルートテーブルと同様、リソース情報(ResourceInfo
)の中に別のリソース情報(AssociationInfo
)が含まれています。
目的のネットワーク ACL を作成するためには以下の 3 つのリソースが必要となります。
CfnNetworkAcl
(AWS::EC2::NetworkAcl)- ネットワーク ACL 本体
CfnNetworkAclEntry
(AWS::EC2::NetworkAclEntry)- ネットワーク ACL に設定するルール
CfnSubnetNetworkAclAssociation
(AWS::EC2::SubnetNetworkAclAssociation)- サブネットの関連付け
インバウンドルールとアウトバウンドルールはどちらも CfnNetworkAclEntry
クラスを利用して生成します。
このクラスが持つプロパティ egress
が true ならばアウトバウンド、false ならばインバウンドのルールとなります。
よって生成時のメソッドに boolean 型の egress
パラメータを持たせ、その値によって処理を分岐させます。(以下ハイライト部分)
private createEntry(scope: cdk.Construct, id: string, networkAcl: CfnNetworkAcl, egress: boolean) { const entry = new CfnNetworkAclEntry(scope, id, { networkAclId: networkAcl.ref, protocol: -1, ruleAction: 'allow', ruleNumber: 100, cidrBlock: '0.0.0.0/0' }); if (egress) entry.egress = true; }
[お詫び]
すみません。書いたあとに気づいたんですが、パラメータの egress
はコンストラクタに直接渡して問題ありません。(ボケてました、ごめんなさい)
private createEntry(scope: cdk.Construct, id: string, networkAcl: CfnNetworkAcl, egress: boolean) { new CfnNetworkAclEntry(scope, id, { networkAclId: networkAcl.ref, protocol: -1, ruleAction: 'allow', ruleNumber: 100, cidrBlock: '0.0.0.0/0', egress: egress }); }
ソースコードはある程度書き溜めているので修正ができず、今回の GitHub では変な形のままとなっています。次回までには修正しておきますのでお許しを。
protocol の -1
は すべてのプロトコル を表します。
You can specify -1 for all protocols.
メインのプログラムはこちら。
ハイライト部分を追記しました。
import * as cdk from '@aws-cdk/core'; import { Vpc } from './resource/vpc'; import { Subnet } from './resource/subnet'; import { InternetGateway } from './resource/internetGateway'; import { ElasticIp } from './resource/elasticIp'; import { NatGateway } from './resource/natGateway'; import { RouteTable } from './resource/routeTable'; import { NetworkAcl } from './resource/networkAcl'; export class DevioStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC const vpc = new Vpc(); vpc.createResources(this); // Subnet const subnet = new Subnet(vpc.vpc); subnet.createResources(this); // Internet Gateway const internetGateway = new InternetGateway(vpc.vpc); internetGateway.createResources(this); // Elastic IP const elasticIp = new ElasticIp(); elasticIp.createResources(this); // NAT Gateway const natGateway = new NatGateway( subnet.public1a, subnet.public1c, elasticIp.ngw1a, elasticIp.ngw1c ); natGateway.createResources(this); // Route Table const routeTable = new RouteTable( vpc.vpc, subnet.public1a, subnet.public1c, subnet.app1a, subnet.app1c, subnet.db1a, subnet.db1c, internetGateway.igw, natGateway.ngw1a, natGateway.ngw1c ); routeTable.createResources(this); // Network ACL const networkAcl = new NetworkAcl( vpc.vpc, subnet.public1a, subnet.public1c, subnet.app1a, subnet.app1c, subnet.db1a, subnet.db1c ); networkAcl.createResources(this); } }
こっちも長くなってきましたね。
ご注意
今回作成したプログラムは変更に弱いため、あまりいいものではありません。「一部のネットワーク ACL のアウトバウンドルールのこの部分だけ変更したい」という要件には対応不可です。(一箇所を変更したらすべてのネットワーク ACL に影響するので)
今回は ネットワーク ACL は基本的に利用しない という思想で共通のプロパティを持つリソースにしましたが、ネットワーク ACL を個別に設定してガンガン利用したい という場合は(長くなっちゃいますが)以下のようにインタフェースを作成し、柔軟に対応してください。
インタフェースの定義。
interface EntryInfo { readonly id: string; readonly protocol: number; readonly ruleAction: string; readonly ruleNumber: number; readonly cidrBlock: string; readonly egress: boolean; } interface ResourceInfo { readonly id: string; readonly resourceName: string; readonly entries: EntryInfo[]; readonly associations: AssociationInfo[]; readonly assign: (networkAcl: CfnNetworkAcl) => void; }
値の設定。
private readonly resources: ResourceInfo[] = [ { id: 'NetworkAclPublic', resourceName: 'nacl-public', entries: [ { id: 'NetworkAclEntryInboundPublic', protocol: -1, ruleAction: 'allow', ruleNumber: 100, cidrBlock: '0.0.0.0/0', egress: false }, { id: 'NetworkAclEntryOutboundPublic', protocol: -1, ruleAction: 'deny', ruleNumber: 101, cidrBlock: '0.0.0.0/0', egress: true } ], associations: [ { id: 'NetworkAclAssociationPublic1a', subnetId: () => this.subnetPublic1a.ref }, { id: 'NetworkAclAssociationPublic1c', subnetId: () => this.subnetPublic1c.ref } ], assign: networkAcl => this.public = networkAcl }, ~ 省略 ~
ルールの生成メソッドはこんな感じで。
private createEntry(scope: cdk.Construct, entryInfo: EntryInfo, networkAcl: CfnNetworkAcl) { new CfnNetworkAclEntry(scope, entryInfo.id, { networkAclId: networkAcl.ref, protocol: entryInfo.protocol, ruleAction: entryInfo.ruleAction, ruleNumber: entryInfo.ruleNumber, cidrBlock: entryInfo.cidrBlock, egress: entryInfo.egress }); }
ループで呼び出します。
private createNetworkAcl(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnNetworkAcl { const networkAcl = new CfnNetworkAcl(scope, resourceInfo.id, { vpcId: this.vpc.ref, tags: [{ key: 'Name', value: this.createResourceName(scope, resourceInfo.resourceName) }] }); for (const entryInfo of resourceInfo.entries) { this.createEntry(scope, entryInfo, networkAcl); } for (const associationInfo of resourceInfo.associations) { this.createAssociation(scope, associationInfo, networkAcl); } return networkAcl; }
その時やりたいことに合わせてカスタマイズしていきましょう。
テスト
テストコードはこちら。
import { expect, countResources, haveResource, anything } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import * as Devio from '../../lib/devio-stack'; test('NetworkAcl', () => { const app = new cdk.App(); const stack = new Devio.DevioStack(app, 'DevioStack'); expect(stack).to(countResources('AWS::EC2::NetworkAcl', 3)); expect(stack).to(haveResource('AWS::EC2::NetworkAcl', { VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-nacl-public' }] })); expect(stack).to(haveResource('AWS::EC2::NetworkAcl', { VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-nacl-app' }] })); expect(stack).to(haveResource('AWS::EC2::NetworkAcl', { VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-nacl-db' }] })); expect(stack).to(countResources('AWS::EC2::NetworkAclEntry', 6)); expect(stack).to(haveResource('AWS::EC2::NetworkAclEntry', { NetworkAclId: anything(), Protocol: -1, RuleAction: 'allow', RuleNumber: 100, CidrBlock: '0.0.0.0/0' })); expect(stack).to(haveResource('AWS::EC2::NetworkAclEntry', { NetworkAclId: anything(), Protocol: -1, RuleAction: 'allow', RuleNumber: 100, CidrBlock: '0.0.0.0/0', Egress: true })); expect(stack).to(countResources('AWS::EC2::SubnetNetworkAclAssociation', 6)); expect(stack).to(haveResource('AWS::EC2::SubnetNetworkAclAssociation', { NetworkAclId: anything(), SubnetId: anything() })); });
以下を確認しています。
- ネットワーク ACL リソースが 3 つあること
- ルールが 6 つあること
- サブネットとネットワーク ACL の関連付けリソースが 6 つあること
- 各リソースのプロパティが正しいこと
確認
マネジメントコンソール上でリソースを確認してみましょう。
できてますよ。
インバウンドルール、アウトバウンドルールも OK。
CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
NetworkAclPublic: Type: AWS::EC2::NetworkAcl Properties: VpcId: Ref: Vpc Tags: - Key: Name Value: devio-stg-nacl-public NetworkAclEntryInboundPublic: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: Ref: NetworkAclPublic Protocol: -1 RuleAction: allow RuleNumber: 100 CidrBlock: 0.0.0.0/0 NetworkAclEntryOutboundPublic: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: Ref: NetworkAclPublic Protocol: -1 RuleAction: allow RuleNumber: 100 CidrBlock: 0.0.0.0/0 Egress: true NetworkAclAssociationPublic1a: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: NetworkAclId: Ref: NetworkAclPublic SubnetId: Ref: SubnetPublic1a NetworkAclAssociationPublic1c: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: NetworkAclId: Ref: NetworkAclPublic SubnetId: Ref: SubnetPublic1c NetworkAclApp: Type: AWS::EC2::NetworkAcl Properties: VpcId: Ref: Vpc Tags: - Key: Name Value: devio-stg-nacl-app NetworkAclEntryInboundApp: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: Ref: NetworkAclApp Protocol: -1 RuleAction: allow RuleNumber: 100 CidrBlock: 0.0.0.0/0 NetworkAclEntryOutboundApp: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: Ref: NetworkAclApp Protocol: -1 RuleAction: allow RuleNumber: 100 CidrBlock: 0.0.0.0/0 Egress: true NetworkAclAssociationApp1a: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: NetworkAclId: Ref: NetworkAclApp SubnetId: Ref: SubnetApp1a NetworkAclAssociationApp1c: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: NetworkAclId: Ref: NetworkAclApp SubnetId: Ref: SubnetApp1c NetworkAclDb: Type: AWS::EC2::NetworkAcl Properties: VpcId: Ref: Vpc Tags: - Key: Name Value: devio-stg-nacl-db NetworkAclEntryInboundDb: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: Ref: NetworkAclDb Protocol: -1 RuleAction: allow RuleNumber: 100 CidrBlock: 0.0.0.0/0 NetworkAclEntryOutboundDb: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: Ref: NetworkAclDb Protocol: -1 RuleAction: allow RuleNumber: 100 CidrBlock: 0.0.0.0/0 Egress: true NetworkAclAssociationDb1a: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: NetworkAclId: Ref: NetworkAclDb SubnetId: Ref: SubnetDb1a NetworkAclAssociationDb1c: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: NetworkAclId: Ref: NetworkAclDb SubnetId: Ref: SubnetDb1c
記録更新です。合計 15 のリソースを作成しました。
なげぇ
GitHub
今回のソースコードは コチラ です。
おわりに
ネットワーク、おしまい!
長かったですね。
以下の順序で作成してきました。(依存関係も書いておきます)
No | リソース | 必要なリソース |
---|---|---|
1 | VPC | なし |
2 | サブネット | VPC |
3 | インターネットゲートウェイ | VPC |
4 | Elastic IP | なし |
5 | NAT ゲートウェイ | サブネット, Elastic IP |
6 | ルートテーブル | VPC, サブネット, インターネットゲートウェイ, NAT ゲートウェイ |
7 | ネットワーク ACL | VPC, サブネット |
今後の予定はこちら。
IAM ロール
セキュリティグループ
EC2
ALB
RDS
楽しみですなぁ
リンク
- class CfnNetworkAcl (construct) | AWS CDK API Reference
- class CfnNetworkAclEntry (construct) | AWS CDK API Reference
- class CfnSubnetNetworkAclAssociation (construct) | AWS CDK API Reference
- AWS::EC2::NetworkAcl | AWS CloudFormation User Guide
- AWS::EC2::NetworkAclEntry | AWS CloudFormation User Guide
- AWS::EC2::SubnetNetworkAclAssociation | AWS CloudFormation User Guide
- ネットワーク ACL | Amazon VPC User Guide